iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0
Modern Web

由前向後,從前端邁向全端系列 第 11

11.【從前端到全端,Nextjs+Nestjs】使用Nx創建component generator

  • 分享至 

  • xImage
  •  

文章重點

  • 建立自定義生成器 (Generator):為了增加效率和一致性,我們創建了自己的生成器。
  • Nx React 生成器的整合:我們將 Nx 的 React 生成器結合到自己的生成器中,使其更具彈性。
  • 模板與組件生成:我們定制了模板文件,使得生成的組件更符合我們的需求。

本文

在本文中,我們專注於使用 Nx 插件和生成器來自動化一些常見的工作流程

首先,我們安裝了必要的 Nx 插件。這些插件為 Nx 提供了核心功能,允許我們擴展和自定義 Nx 的能力

pnpm add @nx/plugin

並且創建一個本地plugin:

pnpm exec nx generate @nx/plugin:plugin plugins 

https://ithelp.ithome.com.tw/upload/images/20230927/20108931V3s9iyQZ6D.png

接著來創建我們的generator (Docs)

pnpm exec nx generate @nx/plugin:generator component --project=plugins

https://ithelp.ithome.com.tw/upload/images/20230927/20108931bqFM08Ueme.png

先修改我們的template結構:
https://ithelp.ithome.com.tw/upload/images/20230927/20108931k51La44ivZ.png

在創建自定義生成器之前,我們先查看了原始的生成器代碼,打開plugins\src\generators\component-generator\generator.ts看一下我們原始的generator:

import { ComponentGeneratorSchema } from "./schema";
import { Tree, addProjectConfiguration, formatFiles, generateFiles } from "@nx/devkit";

import * as path from "path";

export async function componentGenerator(tree: Tree, options: ComponentGeneratorSchema) {
	const projectRoot = `libs/${options.name}`;
	addProjectConfiguration(tree, options.name, {
		root: projectRoot,
		projectType: "library",
		sourceRoot: `${projectRoot}/src`,
		targets: {}
	});
	generateFiles(tree, path.join(__dirname, "files"), projectRoot, options);
	await formatFiles(tree);
}

export default componentGenerator;

componentGenerator
- projectRoot 定義了新項目的根目錄路徑
- 使用 addProjectConfiguration 方法添加新的項目配置到 Nx 的工作空間。
- generateFiles 方法用於根據提供的模板生成文件
- formatFiles 方法將對生成的文件進行格式化

現在我們來修改一下,我們先透過組合@nx/react:generators並且修改我們的generator

import { ComponentGeneratorSchema } from "./schema";
import { Tree, formatFiles, generateFiles } from "@nx/devkit";
import { componentGenerator as reactComponentGenerator } from "@nx/react";

import * as path from "path";

export async function componentGenerator(tree: Tree, options: ComponentGeneratorSchema) {
	const libRoot = `libs/${options.name}`;
	const componentPath = `${libRoot}/src/lib/${options.componentName}`;

	await reactComponentGenerator(tree, {
		project: options.name,
		name: options.componentName,
		pascalCaseFiles: true,
		pascalCaseDirectory: true,
		style: "none"
	});
	updateIndexFile(tree, libRoot, options.componentName);

	generateFiles(tree, path.join(__dirname, "files"), componentPath, {
		...options,
		pascalCasedComponentName: toPascalCase(options.componentName)
	});
	await formatFiles(tree);
}

function updateIndexFile(tree: Tree, libRoot: string, componentName: string) {
	const indexPath = `${libRoot}/src/index.ts`;

	if (!tree.exists(indexPath)) return;

	const pascalCasedComponentName = toPascalCase(componentName);
	const exportStatement = `export * from './lib/${pascalCasedComponentName}';\n`;

	const fileContent = tree.read(indexPath, "utf-8");

	if (!fileContent.includes(exportStatement)) {
		const updatedContent = fileContent + exportStatement;
		tree.write(indexPath, updatedContent);
	}
}

function toPascalCase(string: string): string {
	return string
		.split("-")
		.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
		.join("");
}

export default componentGenerator;

https://ithelp.ithome.com.tw/upload/images/20230927/201089310jRdmcM6Ew.png

  1. componentGenerator
    • 使用 reactComponentGenerator 生成 React 組件
    • 調用 updateIndexFile 函數更新 index.ts 文件以自動導出新的組件
    • 使用 generateFiles 方法生成文件,這次將它放置在新組件的目錄下
  2. updateIndexFile
    確保 index.ts 文件存在,然後將新組件的導出語句添加到文件的末尾
  3. toPascalCase
    將組件名轉換為 PascalCase 格式。例如,my-component 會被轉換為 MyComponent

接下來修改我們的schema:

export interface ComponentGeneratorSchema {
	libName: string;
	name: string;
}

加入plugins\src\generators\component\schema.json:
https://ithelp.ithome.com.tw/upload/images/20230927/20108931UlMgXlv6Qi.png

最後修改我們的template,plugins\src\generators\component\files\index.ts.template:

import <%= pascalCasedComponentName %> from "./<%= pascalCasedComponentName %>";

export default <%= pascalCasedComponentName %>;

接著,我們來試著執行指令:

pnpm exec nx generate @iron-ecommerce-org/plugins:component

https://ithelp.ithome.com.tw/upload/images/20230927/201089313dfzZWi6Yr.png

能看到創建了元件並更新export
https://ithelp.ithome.com.tw/upload/images/20230927/20108931wwA7XHWdEu.png
https://ithelp.ithome.com.tw/upload/images/20230927/20108931iukdOAQpti.png


總結

我們透過使用Nx,成功地創建了自己的自定義react component的生成器。這不僅提高了我們的開發效率,不用一直重新創建,只要鈄過指令來進行創建我們元件。


上一篇
10.【從前端到全端,Nextjs+Nestjs】創建商店頁面-創建頁面與元件
下一篇
12.【從前端到全端,Nextjs+Nestjs】創建商店頁面-實現頁面和元件區塊
系列文
由前向後,從前端邁向全端30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
DL
iT邦新手 5 級 ‧ 2024-02-17 22:54:53

你好,
請問樓主留有此份程式的源碼嗎~?
我跟著做卡關了,
似乎是某些套件依賴的版本其中的語法可能棄用了QQ

Felix阿甫 iT邦研究生 4 級 ‧ 2024-02-18 03:30:20 檢舉

把版本換成固定版本應該就行了

{
	"name": "@iron-ecommerce-org/source",
	"version": "0.0.0",
	"license": "MIT",
	"scripts": {
		"prepare": "husky install",
		"commit": "cz"
	},
	"private": true,
	"dependencies": {
		"@apollo/client": "^3.8.4",
		"@apollo/experimental-nextjs-app-support": "^0.4.3",
		"@apollo/server": "^4.9.4",
		"@devoxa/prisma-relay-cursor-connection": "^3.1.0",
		"@hookform/resolvers": "^3.3.1",
		"@master/css": "2.0.0-beta.173",
		"@master/css-renderer": "2.0.0-beta.160",
		"@master/css.react": "2.0.0-beta.173",
		"@nestjs/apollo": "^12.0.9",
		"@nestjs/common": "^10.0.2",
		"@nestjs/config": "^3.1.1",
		"@nestjs/core": "^10.0.2",
		"@nestjs/graphql": "^12.0.9",
		"@nestjs/jwt": "^10.1.1",
		"@nestjs/passport": "^10.0.2",
		"@nestjs/platform-express": "^10.0.2",
		"@nx/devkit": "16.9.1",
		"@nx/next": "16.8.1",
		"@prisma/client": "^5.4.1",
		"@radix-ui/themes": "2.0.0-rc.3",
		"@swc/helpers": "~0.5.2",
		"apollo-server-plugin-base": "^3.7.2",
		"axios": "^1.0.0",
		"bcrypt": "^5.1.1",
		"class-validator": "^0.14.0",
		"graphql": "^16.8.1",
		"graphql-query-complexity": "^0.12.0",
		"graphql-scalars": "^1.22.4",
		"nestjs-prisma": "^0.22.0",
		"next": "13.4.1",
		"passport": "^0.6.0",
		"passport-jwt": "^4.0.1",
		"react": "18.2.0",
		"react-dom": "18.2.0",
		"react-hook-form": "^7.46.2",
		"recoil": "^0.7.7",
		"reflect-metadata": "^0.1.13",
		"rxjs": "^7.8.0",
		"tslib": "^2.3.0",
		"zod": "3.21.4"
	},
	"devDependencies": {
		"@babel/core": "^7.14.5",
		"@babel/preset-react": "^7.14.5",
		"@commitlint/cli": "^17.7.1",
		"@commitlint/config-conventional": "^17.7.0",
		"@faker-js/faker": "^8.1.0",
		"@graphql-codegen/cli": "^5.0.0",
		"@graphql-codegen/typescript-operations": "^4.0.1",
		"@graphql-codegen/typescript-react-apollo": "^4.0.0",
		"@nestjs/schematics": "^10.0.1",
		"@nestjs/testing": "^10.0.2",
		"@nx/eslint-plugin": "16.8.1",
		"@nx/jest": "16.9.1",
		"@nx/js": "16.9.1",
		"@nx/linter": "16.8.1",
		"@nx/nest": "^16.9.1",
		"@nx/node": "16.9.1",
		"@nx/playwright": "16.8.1",
		"@nx/plugin": "16.9.1",
		"@nx/react": "16.8.1",
		"@nx/storybook": "16.8.1",
		"@nx/vite": "16.8.1",
		"@nx/web": "16.8.1",
		"@nx/webpack": "16.9.1",
		"@nx/workspace": "16.8.1",
		"@playwright/test": "^1.36.0",
		"@storybook/addon-essentials": "7.4.3",
		"@storybook/addon-interactions": "^7.2.1",
		"@storybook/addon-links": "^7.4.4",
		"@storybook/addon-onboarding": "^1.0.8",
		"@storybook/addon-styling": "^1.3.7",
		"@storybook/addon-themes": "^7.4.4",
		"@storybook/core-server": "7.4.3",
		"@storybook/jest": "~0.1.0",
		"@storybook/react": "7.4.3",
		"@storybook/react-vite": "7.4.3",
		"@storybook/test-runner": "^0.13.0",
		"@storybook/testing-library": "~0.2.0",
		"@swc-node/register": "~1.4.2",
		"@swc/cli": "~0.1.62",
		"@swc/core": "~1.3.85",
		"@testing-library/dom": "^9.3.3",
		"@testing-library/jest-dom": "^6.1.3",
		"@testing-library/react": "14.0.0",
		"@testing-library/user-event": "^14.5.1",
		"@types/jest": "^29.4.0",
		"@types/node": "18.14.2",
		"@types/react": "18.2.14",
		"@types/react-dom": "18.2.6",
		"@types/testing-library__jest-dom": "^6.0.0",
		"@typescript-eslint/eslint-plugin": "^5.60.1",
		"@typescript-eslint/parser": "^5.60.1",
		"babel-jest": "^29.4.1",
		"commitizen": "^4.3.0",
		"cz-git": "^1.7.1",
		"eslint": "~8.46.0",
		"eslint-config-next": "13.4.1",
		"eslint-config-prettier": "8.1.0",
		"eslint-plugin-import": "2.27.5",
		"eslint-plugin-jsx-a11y": "6.7.1",
		"eslint-plugin-playwright": "^0.15.3",
		"eslint-plugin-react": "7.32.2",
		"eslint-plugin-react-hooks": "4.6.0",
		"husky": "^8.0.3",
		"jest": "^29.4.1",
		"jest-environment-jsdom": "^29.4.1",
		"jest-environment-node": "^29.4.1",
		"lint-staged": "^14.0.1",
		"nx": "16.8.1",
		"prettier": "^2.6.2",
		"prisma": "^5.4.1",
		"ts-jest": "^29.1.0",
		"ts-morph": "^20.0.0",
		"ts-node": "10.9.1",
		"typescript": "~5.1.3",
		"vite": "~4.3.9"
	},
	"config": {
		"commitizen": {
			"path": "cz-git"
		}
	},
	"prisma": {
		"seed": "ts-node apps/iron-ecommerce-server/prisma/seed.ts"
	}
}
DL iT邦新手 5 級 ‧ 2024-02-18 13:13:13 檢舉

謝謝大大

我要留言

立即登入留言